/*
The MIT License (MIT)

Copyright (c) 2013 Mike Dapiran, Brian May, Richard Pospesel, and Bert Wierenga

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#include "hhdIGame.h"
#include "hhdTPrefab.h"
#include "hhdTScene.h"
#include "hhdContentLibrary.h"
#include "hhdFactory.h"
#include "hhdMessenger.h"
#include "hhdPhysics.h"
#include "hhdCommon.h"
#include "hhdIGfxViewport.h"
#include "hhdGfxApi.h"
#include "hhdThread.h"

#if HHD_PLATFORM_WINDOWS
#include <crtdbg.h>
#endif //HHD_PLATFORM_WINDOWS

namespace hhd
{
    IGame* IGame::_game = NULL;
    String IGame::_previous_scene_path = "";

    IGame::IGame()
        : _reload_scene(false)
        , _change(false)
        , _transist(false)
        , _start_scene("NOT SET")
		, _quit_signal(false)
		, _current_scene(NULL)
		, _system_paused(false)
		, _last_draw_time(0.0f)
		, _target_framerate(60)
    {
        IGame::_game = this;
        IGame::_game->_transist = false;
    }

    IGame::~IGame()
    {
    }

    void IGame::initialize()
    {
        //do config first
        configure();
        initializeLibraries();
    }

    void IGame::initializeLibraries()
    {
        //baw	Get anything that needs to be setup in the common utils ready.
        hhd::commonInitialize();

        // initialize the core engine
        hhd::Factory::initialize();
        hhd::Messenger::initialize();

        // register core content
        ContentLibrary::registerContentType<TScene>();
        ContentLibrary::registerContentType<TPrefab>();

        // initialize engine libraries (rendering, etc)
        initializeEngineLibraries();

        //baw	initialize anything the deriving game needs setup libs-wise using the core API.
        initializeGameplayLibraries();

        // this needs to happen last since libraries may define content types
        hhd::ContentLibrary::initialize();

        // load any requested content
        loadContent();
        _game_timer.startTimer();

        // load and setup the start scene
        safeChangeToScene(_start_scene);
    }

#ifdef _DEBUG
#define HEAP_CHECK_ASSERT _ASSERTE( _CrtCheckMemory( ) );
#else
#define HEAP_CHECK_ASSERT
#endif
    void IGame::finalize()
    {
        HEAP_CHECK_ASSERT
        finalizeGameplayLibraries();

        HEAP_CHECK_ASSERT
        finalizeEngineLibraries();

        HEAP_CHECK_ASSERT
        hhd::ContentLibrary::finalize();

        HEAP_CHECK_ASSERT
        hhd::Messenger::finalize();

        HEAP_CHECK_ASSERT
        hhd::Factory::finalize();

        HEAP_CHECK_ASSERT
        hhd::commonFinalize();

        HEAP_CHECK_ASSERT
    }

    SmartPtr<TScene> IGame::getCurrentScene() const
    {
        return _current_scene;
    }

    void IGame::exit()
    {
        _quit_signal = true;
    }

    const String IGame::loadScene(const String& in_filepath)
    {
        //load the Scene via content library
        SmartPtr<TScene> scene = SmartPtr<IContent>::castTo<TScene>( ContentLibrary::loadContent<TScene>(in_filepath) );

        //if it's already been loaded, initialized, or ran then we need to reload it because this tells us
        //we were returned a scene that we already loaded.

        if( scene->_scene_processes_completed & (SPC_LOADED | SPC_INITIALIZED | SPC_HAS_BEEN_RUN) )
        {
            String path = scene->getPath();
            IGame::_game->safeReloadScene(path);
            scene = ContentLibrary::getScene(path);
            return scene->getPath();
        }
        else
        {
            scene->_scene_processes_completed |= SPC_LOADED;
            scene->initialize();
            scene->_scene_processes_completed |= SPC_INITIALIZED;
            scene->_parent_game = IGame::_game;

			return scene->getPath();
        }
    }

    bool IGame::unloadScene(const String& in_path)
    {
        bool null_out = false;

        if( IGame::_game->_current_scene && in_path.equals(IGame::_game->_current_scene->getPath()) )
        {
            null_out = true;
        }

        //use the key to get the Scene from the ContentLibrary and then release it back to
        // the ContentLibrary directly.
        ContentLibrary::releaseScene(in_path);

        if( null_out )
        {
            IGame::_game->_current_scene = NULL;
        }

        return null_out;
    }

    void IGame::changeScene(const String& in_filepath)
    {
        IGame::_game->_new_scene_filepath = in_filepath;
        IGame::_game->_change = true;
    }

    void IGame::transistScene(const String& in_filepath)
    {
        IGame::_game->_new_scene_filepath = in_filepath;
        IGame::_game->_transist = true;
    }

    void IGame::reloadScene(const String& in_path)
    {
        IGame::_game->_reload_scene_key = in_path;
        IGame::_game->_reload_scene = true;
    }

    void IGame::safeReloadScene(const String& in_path)
    {
        SmartPtr<TScene> scene = ContentLibrary::getScene(in_path);
        HHD_ASSERT(scene);
        //get the path so we can reload it
        String scene_path = scene->getPath();

        //unload it
        IGame::unloadScene( scene_path );

        //reload it
        String new_key = IGame::loadScene(scene_path);

        IGame::_game->_current_scene = ContentLibrary::getScene( new_key );
        IGame::_game->_reload_scene = false;
    }

    void IGame::safeTransistToScene(const String& in_filepath)
    {
        //get current scene name
        String current_path = _game->getCurrentScene()->getPath();

        //unload old scene
        unloadScene(current_path);

        //load new scene
        String new_key = loadScene(in_filepath);

        //switch to new scene
        SmartPtr<TScene> scene = ContentLibrary::getScene(new_key);
        if(scene)
        {
            IGame::_game->_current_scene = scene;
            IGame::_game->_current_scene->resume();
            _previous_scene_path = ContentLibrary::getRelativePath(scene->getPath());
        }
        IGame::_game->_transist = false;
        IGame::_game->_game_timer.updateTick();
        IGame::_game->_game_timer.updateTick();

        IGame::_game->_game_timer.drawTick();
        IGame::_game->_game_timer.drawTick();
    }

    void IGame::safeChangeToScene(const String& in_filepath)
    {
        //load new scene
        String new_key = loadScene(in_filepath);

        //switch to new scene
        SmartPtr<TScene> scene = ContentLibrary::getScene(new_key);
        if(scene)
        {
            IGame::_game->_current_scene = scene;
            IGame::_game->_current_scene->resume();
            _previous_scene_path = ContentLibrary::getRelativePath(scene->getPath());
        }
        IGame::_game->_change = false;
        IGame::_game->_game_timer.updateTick();
        IGame::_game->_game_timer.updateTick();

        IGame::_game->_game_timer.drawTick();
        IGame::_game->_game_timer.drawTick();
    }

    void IGame::run()
    {
        //baw	must have a scene to run or else what are we doing?
        HHD_ASSERT( _current_scene != static_cast<TScene*>(NULL) );
		_game_timer.startTimer();

		while(!_quit_signal)
		{
			// OS update
			updateOS();

			// make sure we're ready for this
			updateTransitions();

			//check for system paused
			//if(_system_paused)
			//{
			//	Sleep(0);
			//	continue;
			//}

			update(_game_timer);
			_game_timer.updateTick();

			draw();
			_game_timer.drawTick();
			_last_draw_time = _game_timer.getGameTime();

			_current_scene->_scene_phase = None;
		}
    }

	void IGame::updateGame()
	{

	}

	void IGame::updateTransitions()
	{
		//scene transition
		if( _transist )
		{
			safeTransistToScene( _new_scene_filepath );
		}
		//scene change
		else if( _change )
		{
			safeChangeToScene( _new_scene_filepath );
		}
		else if( _reload_scene )
		{
			safeReloadScene( _reload_scene_key );
		}
	}

    void IGame::pauseSystem()
    {
        _system_paused = true;
        if(_current_scene)
        {
            _current_scene->pause();
        }

        _game_timer.pause();
    }

    void IGame::unPauseSystem()
    {
        _system_paused = false;

        if(_current_scene)
        {
            _current_scene->resume();
        }

        _game_timer.unPause();
    }

    void IGame::update(const GameTimer& in_timer)
    {
        HHD_ASSERT(_current_scene != static_cast<TScene*>(NULL));

		_current_scene->update(in_timer);
		_current_scene->_scene_processes_completed |= SPC_HAS_BEEN_RUN;
		_current_scene->_scene_phase = MessageHandling;
		Messenger::handleMessages();
    }

    void IGame::draw()
    {
		HHD_ASSERT(_current_scene != static_cast<TScene*>(NULL));
		_current_scene->_scene_phase = Draw;

        for(size_t i = 0; i < _manager_predraw_callbacks.size(); i++)
        {
            (*_manager_predraw_callbacks[i])();
        }

        if(_current_scene != static_cast<TScene*>(NULL))
        {
            _current_scene->draw();
        }
    }

    void IGame::registerWindowsEventCallback(void* in_proc)
    {
        _manager_os_message_callbacks.add( in_proc );
    }

    void IGame::getPreviousScenePath(String& out_key)
    {
        out_key = IGame::_previous_scene_path;
    }

    void IGame::initializeEngineLibraries()
    {
        GfxViewportConfig viewport_config;
        viewport_config.viewport_type = VT_Windowed;
        viewport_config.pixel_width = 960;
        viewport_config.pixel_height = 540;
        viewport_config.x_location = 0;
        viewport_config.y_location = 0;
        viewport_config.name = "hhd";
		viewport_config.os_func = getOsMessageConsumeFunction();
		viewport_config.game_ptr = this;

        gfxInitialize(viewport_config);
		BulletPhysics::initialize();

		//baw	Need to get platform agnostic APIs setup first for these subsystems
        //Input::initialize();
        //baw	gonna need to figure this one out
        //Audio::
    }

    void IGame::finalizeEngineLibraries()
    {
		gfxFinalize();
        BulletPhysics::finalize();

		//Input::finalize();
    }
}
